import Rect, {Align, Direction} from "./rect"

/**
 * 将 dom 组件，放置在 ref 的相对位置。
 *
 * E.G.: 各类吸附窗口
 *
 * @param dom - 弹窗自身
 * @param ref - 定位组件
 * @param direct - 弹窗方向，dom 相对于 ref 的方向
 * @param align - 对齐方式，dom 相对于 ref 的对齐方式
 * @param offset - 偏移量，边缘留白
 * @returns 由 left, top 构成的数组
 */
const openOf = (dom: Rect, ref: Rect, direct: Direction, align: Align, offset: number = 0): [number, number] => {
    let l = 0, t = 0;

    if (direct === 'top') {
        t = ref.top - dom.height - offset;
    } else if (direct === 'bottom') {
        t = ref.bottom + offset;
    } else if (direct === 'left') {
        l = ref.left - dom.width - offset;
    } else if (direct === 'right') {
        l = ref.right + offset;
    }

    if (direct === 'bottom' || direct === 'top') {
        l = getLeftAlign(dom, ref, align);
    } else {
        t = getTopAlign(dom, ref, align);
    }

    return [l, t]
}

/**
 * 判断组件是否超出限制区域
 *
 * 此函数用于优化展示，如果弹窗超出限制区域，需要重新设置组件位置
 *
 * @param location - 推荐位置
 * @param dom - 弹窗自身，用到了宽、高属性
 * @param box - 限制区域
 * @returns 是否超出限制区域，等值判定为未超出
 */
const isOutOfBox = (location: [number, number], dom: Rect, box: Rect): boolean => {
    const left = location[0], top = location[1];
    const right = left + dom.width, bottom = top + dom.height;
    if (left < box.left) {
        return true;
    } else if (right > box.right) {
        return true;
    } else if (top < box.top) {
        return true;
    } else {
        return bottom > box.bottom;
    }
}


/**
 * 将 dom 组件，放置在 box 内部。
 *
 * E.G.: 主页的消息弹窗，一般位于 body 的 bottom-end 位置
 *
 * @param dom - 弹窗自身
 * @param box - 限制区域
 * @param direct - 弹窗方向，dom 相对于 ref 的方向
 * @param align - 对齐方式，dom 相对于 ref 的对齐方式
 * @param offset - 偏移量，边缘留白
 * @returns 由 left, top 构成的数组
 */
const openIn = (dom: Rect, box: Rect, direct: Direction, align: Align, offset: number = 0): [number, number] => {
    // 相对于屏幕的 left/top
    let l = 0, t = 0;

    if (direct === 'bottom') {
        t = box.bottom - dom.height - offset;
    } else if (direct === 'top') {
        t = box.top + offset;
    } else if (direct === 'left') {
        l = box.left + offset;
    } else if (direct === 'right') {
        l = box.left + box.width - dom.width - offset;
    }

    if (direct === 'bottom' || direct === 'top') {
        if (align === 'start') {
            l = box.left + offset;
        } else if (align === 'end') {
            l = box.left + box.width - dom.width - offset;
        } else {
            l = box.left + box.width / 2 - dom.width / 2;
        }
    } else {
        if (align === 'start') {
            t = box.top + offset;
        } else if (align === 'end') {
            t = box.bottom - dom.height - offset;
        } else {
            t = box.top + box.height / 2 - dom.height / 2;
        }
    }
    return [l, t];
}

/**
 * 右下角弹窗
 *
 * E.G.：右击菜单，浏览器右击的时候，右下角弹出菜单面板，如果达到临界值，则将菜单展示在左下角（相对于点击位置）
 *
 * 自动微调属性，始终保证 dom 不会展示在容器外面。
 *
 * 这是简化逻辑后的一个函数，其它函数也能实现这一效果。
 *
 * @param dom - 弹窗自身
 * @param box - 限制区域
 * @param left - 相对于屏幕的 left
 * @param top  - 相对于屏幕的 top
 * @returns 由 left, top 构成的数组
 */
const openInRightBottom = (dom: Rect, box: Rect, left: number, top: number): [number, number] => {
    // top 不足以放下 dom 组件 
    if (top + dom.height > box.bottom) {
        top -= dom.height;
    }

    // left 不足以放下 dom 组件
    if (left + dom.width > box.left) {
        left -= dom.width;
    }
    return [left, top];
}

/**
 * 将 dom 组件，放置在 ref 的相对位置，并且限制在 box 内部。
 *
 * E.G.: 各类吸附窗口
 *
 * 临界值：刚好相等的时候，不会调转气泡方向
 *
 * @param dom - 弹窗自身
 * @param ref - 定位组件
 * @param box - 限制区域
 * @param direct - 弹窗方向，dom 相对于 ref 的方向
 * @param align - 对齐方式，dom 相对于 ref 的对齐方式
 * @param offset - 偏移量，边缘留白
 * @returns 由 left, top 构成的数组
 */
const openInBoxOf = (dom: Rect, ref: Rect, box: Rect, direct: Direction, align: Align, offset: number = 0): [number, number] => {
    let l = 0, t = 0;

    if (direct === 'top') {
        t = ref.top - dom.height - offset;
        if (t < box.top) {
            t = ref.bottom + offset;
        }
    } else if (direct === 'bottom') {
        t = ref.bottom + offset;
        if (t > box.bottom) {
            t = ref.top - dom.height - offset;
        }
    } else if (direct === 'left') {
        l = ref.left - dom.width - offset;
        if (l < box.left) {
            l = ref.right + offset;
        }
    } else if (direct === 'right') {
        l = ref.right + offset;
        if (l > box.right) {
            l = ref.left - dom.width - offset;
        }
    }

    if (direct === 'bottom' || direct === 'top') {
        l = getLeftAlign(dom, ref, align);
    } else {
        t = getTopAlign(dom, ref, align);
    }

    return [l, t];
}

/**
 * 组件水平方向上对齐的时候，获取组件的 left 坐标
 *
 * @param dom - 弹窗自身
 * @param ref - 定位组件
 * @param align - 对齐方式
 * @returns 由 left, top 构成的数组
 */
const getLeftAlign = (dom: Rect, ref: Rect, align: Align) => {
    if (align === 'start') {
        return ref.left;
    } else if (align === 'end') {
        return ref.left + ref.width - dom.width;
    } else {
        return ref.left + ref.width / 2 - dom.width / 2;
    }
}

/**
 * 组件垂直方向上对齐的时候，获取组件的 top 坐标
 *
 * @param dom - 弹窗自身
 * @param ref - 定位组件
 * @param align - 对齐方式
 * @returns 由 left, top 构成的数组
 */
const getTopAlign = (dom: Rect, ref: Rect, align: Align) => {
    if (align === 'start') {
        return ref.top;
    } else if (align === 'end') {
        return ref.bottom - dom.height;
    } else {
        return ref.top + ref.height / 2 - dom.height / 2;
    }
}

/**
 * 用于确定气泡箭头的朝向
 *
 * 获取组件的朝向：ref 组件相对于 dom 组件的方向（也就是相对于 dom组件，箭头要放置的位置）
 *
 * 有限制区域的弹窗，达到临界值的时候，弹窗会悬浮于相反的方向。
 *
 * 临界值：刚好相等的时候，不会调转气泡方向
 *
 * @param dom 气泡窗的坐标
 * @param ref 定位组件的坐标
 * @param direct - 弹窗方向，dom 相对于 ref 的方向
 * @returns 弹窗方向，dom 相对于 ref 的方向
 */
const getDirection = (dom: [number, number], ref: [number, number], direct: Direction): Direction => {
    if (direct === "top" || 'bottom') {
        return dom[1] >= ref[1] ? "top" : "bottom";
    } else {
        return dom[0] >= ref[0] ? "left" : "right";
    }
}

export default {
    // 获取对齐属性
    getLeftAlign, getTopAlign, getDirection, isOutOfBox,

    // 展开弹出层到指定位置
    openIn, openOf, openInBoxOf, openInRightBottom
}

